﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Framework;
using Umbraco.Framework.Context;
using Umbraco.Framework.DataManagement.Linq.QueryModel;
using Umbraco.Framework.DataManagement.Linq.ResultBinding;
using Umbraco.Framework.Persistence;
using Umbraco.Framework.Persistence.Model;
using Umbraco.Framework.Persistence.Model.Associations;
using Umbraco.Framework.Persistence.Model.Associations._Revised;
using Umbraco.Framework.Persistence.Model.Attribution;
using Umbraco.Framework.Persistence.Model.Constants;
using Umbraco.Framework.Persistence.Model.Constants.AttributeDefinitions;
using Umbraco.Framework.Persistence.ProviderSupport._Revised;
using Umbraco.Hive.ProviderSupport;
using Umbraco.Hive.Providers.WordPress.Entity.Model;
using Umbraco.Hive.Providers.WordPress.Schema.Model;
using Umbraco.Hive.Providers.WordPress.Schema.Model.AttributeDefinitions;
using Umbraco.Hive.Providers.WordPress.Schema.Model.AttributeGroups;
using NodeNameAttributeDefinition = Umbraco.Hive.Providers.WordPress.Schema.Model.AttributeDefinitions.NodeNameAttributeDefinition;

namespace Umbraco.Hive.Providers.WordPress.Entity
{
    public class Repository : AbstractEntityRepository
    {
        private Regex _hiveIdPattern = new Regex("^(.*?)-([0-9]*?)$", RegexOptions.Compiled | RegexOptions.Singleline);

        private dynamic _postLoader;
        private dynamic _categoryLoader;

        public Repository(ProviderMetadata providerMetadata, AbstractSchemaRepository schemas, IFrameworkContext frameworkContext)
            : base(providerMetadata, schemas, frameworkContext)
        { }

        #region Properties

        /// <summary>
        /// Gets a value indicating whether this instance can read relations.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance can read relations; otherwise, <c>false</c>.
        /// </value>
        public override bool CanReadRelations { get { return true; } }

        /// <summary>
        /// Gets a value indicating whether this instance can write relations.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can write relations; otherwise, <c>false</c>.
        /// </value>
        public override bool CanWriteRelations { get { return false; } }

        /// <summary>
        /// Gets the post loader.
        /// </summary>
        private dynamic PostLoader { get { return _postLoader ?? (_postLoader = new Post()); } }

        /// <summary>
        /// Gets the post loader.
        /// </summary>
        private dynamic CategoryLoader { get { return _categoryLoader ?? (_categoryLoader = new Category()); } }

        #endregion

        #region Entity

        /// <summary>
        /// Identifies if a <see cref="!:TEntity"/> with matching <paramref name="id"/> can be found in this repository.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam><param name="id">The id.</param>
        /// <returns>
        /// <code>
        /// true
        /// </code>
        ///  if the item with <paramref name="id"/> can be found, otherwise 
        /// <code>
        /// false
        /// </code>
        /// .
        /// </returns>
        public override bool Exists<TEntity>(HiveId id)
        {
            return Get<TEntity>(true, id).Any();
        }

        /// <summary>
        /// Performs the add or update.
        /// </summary>
        /// <param name="entity">The entity.</param>
        protected override void PerformAddOrUpdate(TypedEntity entity)
        {
            return;
        }

        /// <summary>
        /// Performs the delete.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="id">The id.</param>
        protected override void PerformDelete<T>(HiveId id)
        {
            var match = _hiveIdPattern.Match(id.Value.ToString());
            if (match.Success)
            {
                switch (match.Groups[1].Value)
                {
                    case "Post":
                        PostLoader.Delete(Convert.ToInt32(match.Groups[2].Value));
                        break;
                    case "Category":
                        CategoryLoader.Delete(Convert.ToInt32(match.Groups[2].Value));
                        break;
                }
            }
        }

        /// <summary>
        /// Performs the get.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="allOrNothing">if set to <c>true</c> [all or nothing].</param>
        /// <param name="ids">The ids.</param>
        /// <returns></returns>
        protected override IEnumerable<T> PerformGet<T>(bool allOrNothing, params HiveId[] ids)
        {
            // Posts
            foreach (var hiveId in ids.Where(x => x.Value.Type == HiveIdValueTypes.String).Select(x => x.Value.ToString()))
            {
                var match = _hiveIdPattern.Match(hiveId);
                if(match.Success)
                {
                    switch (match.Groups[1].Value)
                    {
                        case "Post":
                            yield return ConvertPostToTypedEntity(PostLoader.Single(Convert.ToInt32(match.Groups[2].Value))) as T;
                            break;
                        case "Category":
                            yield return ConvertCategoryToTypedEntity(CategoryLoader.Single(Convert.ToInt32(match.Groups[2].Value))) as T;
                            break;
                    }
                }
            }
        }

        /// <summary>
        /// Performs the get all.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        protected override IEnumerable<T> PerformGetAll<T>()
        {
            // Posts
            dynamic postLoader = new Post();
            var posts = postLoader.All();
            foreach (var post in posts)
            {
                yield return ConvertPostToTypedEntity(post);
            }

            // Categories
            dynamic categoryLoader = new Category();
            var categories = categoryLoader.All();
            foreach (var category in categories)
            {
                yield return ConvertCategoryToTypedEntity(category);
            }
        }

        // ----------------------------------------------------------- //

        /// <summary>
        /// Performs the execute many.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="query">The query.</param>
        /// <param name="objectBinder">The object binder.</param>
        /// <returns></returns>
        public override IEnumerable<T> PerformExecuteMany<T>(QueryDescription query, ObjectBinder objectBinder)
        {
            return Enumerable.Empty<T>();
        }

        /// <summary>
        /// Performs the execute scalar.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="query">The query.</param>
        /// <param name="objectBinder">The object binder.</param>
        /// <returns></returns>
        public override T PerformExecuteScalar<T>(QueryDescription query, ObjectBinder objectBinder)
        {
            return default(T);
        }

        /// <summary>
        /// Performs the execute single.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="query">The query.</param>
        /// <param name="objectBinder">The object binder.</param>
        /// <returns></returns>
        public override T PerformExecuteSingle<T>(QueryDescription query, ObjectBinder objectBinder)
        {
            return default(T);
        }
        
        #endregion

        #region Relations

        /// <summary>
        /// Adds the relation.
        /// </summary>
        /// <param name="item">The item.</param>
        public override void AddRelation(IReadonlyRelation<IRelatableEntity, IRelatableEntity> item)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Removes the relation.
        /// </summary>
        /// <param name="item">The item.</param>
        public override void RemoveRelation(IRelationById item)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Performs the get parent relations.
        /// </summary>
        /// <param name="childId">The child id.</param>
        /// <param name="relationType">Type of the relation.</param>
        /// <returns></returns>
        public override IEnumerable<IRelationById> PerformGetParentRelations(HiveId childId, RelationType relationType = null)
        {
            if (childId.EqualsIgnoringProviderId(FixedHiveIds.ContentVirtualRoot)) 
                yield break; // If we are at the root, then don't bother checking for any parent relations

            var match = _hiveIdPattern.Match(childId.Value.ToString());
            if (match.Success)
            {
                switch (match.Groups[1].Value)
                {
                    case "Post": 
                        // Posts don't have a heirachical structure, so always relate to root
                        yield return new RelationById(FixedHiveIds.ContentVirtualRoot, childId, FixedRelationTypes.ContentTreeRelationType, 0);
                        break;
                    case "Category":
                        var childCategory = CategoryLoader.Single(term_id: Convert.ToInt32(match.Groups[2].Value));
                        var childCategoryId = new HiveId("Category-" + childCategory.term_id);
                        var parentCategoryId = (childCategory.parent == 0) 
                            ? FixedHiveIds.ContentVirtualRoot 
                            : new HiveId("Category-" + childCategory.parent);
                        yield return new RelationById(parentCategoryId, childCategoryId, FixedRelationTypes.ContentTreeRelationType, 0);
                        break;
                }
            }
        }

        /// <summary>
        /// Performs the get child relations.
        /// </summary>
        /// <param name="parentId">The parent id.</param>
        /// <param name="relationType">Type of the relation.</param>
        /// <returns></returns>
        public override IEnumerable<IRelationById> PerformGetChildRelations(HiveId parentId, RelationType relationType = null)
        {
            // Ideally we'd have different root nodes for Posts and Categories but for now just return both at content root
            if (parentId == FixedHiveIds.ContentVirtualRoot)
            {
                if (relationType == null || relationType.RelationName == FixedRelationTypes.ContentTreeRelationType.RelationName)
                {
                    // Posts
                    var posts = PostLoader.All();
                    foreach (var post in posts)
                    {
                        yield return new RelationById(parentId, new HiveId("Post-" + post.ID), FixedRelationTypes.ContentTreeRelationType, 0);
                    }

                    // Categories
                    var categories = CategoryLoader.All(parent: 0);
                    foreach (var category in categories)
                    {
                        yield return new RelationById(parentId, new HiveId("Category-" + category.term_id), FixedRelationTypes.ContentTreeRelationType, 0);
                    }
                }
            }
            else
            {
                var match = _hiveIdPattern.Match(parentId.Value.ToString());
                if (match.Success)
                {
                    switch (match.Groups[1].Value)
                    {
                        case "Post":
                            // Posts don't have a heirachical structure, so don't have any child relations
                            break;
                        case "Category":
                            if (relationType == null || relationType.RelationName == FixedRelationTypes.ContentTreeRelationType.RelationName)
                            {
                                var categories = CategoryLoader.All(parent: Convert.ToInt32(match.Groups[2].Value));
                                foreach (var category in categories)
                                {
                                    yield return new RelationById(parentId, new HiveId("Category-" + category.term_id), FixedRelationTypes.ContentTreeRelationType, 0);
                                }
                            }
                            break;
                    }
                }
            }
        }

        /// <summary>
        /// Performs the get ancestor relations.
        /// </summary>
        /// <param name="descendentId">The descendent id.</param>
        /// <param name="relationType">Type of the relation.</param>
        /// <returns></returns>
        public override IEnumerable<IRelationById> PerformGetAncestorRelations(HiveId descendentId, RelationType relationType = null)
        {
            return GetParentRelations(descendentId, relationType).SelectRecursive(x => GetParentRelations(x.SourceId, relationType));
        }

        /// <summary>
        /// Performs the get descendent relations.
        /// </summary>
        /// <param name="ancestorId">The ancestor id.</param>
        /// <param name="relationType">Type of the relation.</param>
        /// <returns></returns>
        public override IEnumerable<IRelationById> PerformGetDescendentRelations(HiveId ancestorId, RelationType relationType = null)
        {
            return GetChildRelations(ancestorId, relationType).SelectRecursive(x => GetChildRelations(x.DestinationId, relationType));
        }

        /// <summary>
        /// Performs the find relation.
        /// </summary>
        /// <param name="sourceId">The source id.</param>
        /// <param name="destinationId">The destination id.</param>
        /// <param name="relationType">Type of the relation.</param>
        /// <returns></returns>
        public override IRelationById PerformFindRelation(HiveId sourceId, HiveId destinationId, RelationType relationType)
        {
            throw new NotImplementedException();
        }
        
        #endregion

        #region Helper Methods

        /// <summary>
        /// Gets the post.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        private static TypedEntity ConvertPostToTypedEntity(dynamic item)
        {
            if (item == null) 
                return null;

            var output = new TypedEntity()
            {
                Id = new HiveId("Post-" + item.ID),
                EntitySchema = new PostSchema(),
                UtcCreated = item.post_modified,
                UtcModified = item.post_modified,
                UtcStatusChanged = item.post_modified
            };

            var nameAttribute = new TypedAttribute(new NodeNameAttributeDefinition(), item.post_title);
            nameAttribute.Values.Add("Name", item.post_title);
            nameAttribute.Values.Add("UrlName", item.post_name);
            nameAttribute.Id = new HiveId(nameAttribute.AttributeDefinition.Alias);
            output.Attributes.Add(nameAttribute);

            var contentAttribute = new TypedAttribute(new ContentAttributeDefinition(), item.post_content);
            contentAttribute.Id = new HiveId("post_content");
            output.Attributes.Add(contentAttribute);

            var authorAttribute = new TypedAttribute(new AuthorAttributeDefinition(), item.author);
            authorAttribute.Id = new HiveId("post_author");
            output.Attributes.Add(authorAttribute);

            var templateAttribute = new TypedAttribute(new SelectedTemplateAttributeDefinition(new PostDetailsAttributeGroup()), "WordPressPost.cshtml");
            templateAttribute.Id = new HiveId("selected_template");
            output.Attributes.Add(templateAttribute);

            return output;
        }

        /// <summary>
        /// Converts the category to typed entity.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        private static TypedEntity ConvertCategoryToTypedEntity(dynamic item)
        {
            if (item == null)
                return null;

            var output = new TypedEntity()
            {
                Id = new HiveId("Category-" + item.term_id),
                EntitySchema = new CategorySchema(),
                UtcCreated = DateTime.Now,
                UtcModified = DateTime.Now,
                UtcStatusChanged = DateTime.Now
            };

            var nameAttribute = new TypedAttribute(new NodeNameAttributeDefinition(), item.name);
            nameAttribute.Values.Add("Name", item.name);
            nameAttribute.Values.Add("UrlName", item.slug);
            nameAttribute.Id = new HiveId(nameAttribute.AttributeDefinition.Alias);
            output.Attributes.Add(nameAttribute);

            var templateAttribute = new TypedAttribute(new SelectedTemplateAttributeDefinition(new PostDetailsAttributeGroup()), "WordPressCategory.cshtml");
            templateAttribute.Id = new HiveId("selected_template");
            output.Attributes.Add(templateAttribute);

            return output;
        }
        
        #endregion

        /// <summary>
        /// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
        /// </summary>
        protected override void DisposeResources()
        {
            return;
        }
    }
}
